package comet

import (
	"gddgame.cc/galaxy/satellite"
	"gddgame.cc/galaxy/satellite/compress/snappy"
	"gddgame.cc/galaxy/satellite/transport/kcp"
	"gddgame.cc/galaxy/satellite/transport/tcp"
	"gddgame.cc/galaxy/satellite/transport/ws"
	"gddgame.cc/galaxy/utils/cache"
	"gddgame.cc/galaxy/utils/system"

	"fmt"
	"net"
	"strings"

	"gddgame.cc/galaxy/utils/def"
)

type Next func(err error) error
type AuthHandle func(context def.Context, packet *AppPacket, authorization string, prevAuthority interface{}) (interface{}, error)

type Interface interface {
	Connect(socket *Socket) error
	Disconnect(socket *Socket, reason string) error
}

type InterfaceHandler func(DN, Interface)

type Server struct {
	logger def.Logger

	cache *cache.MemoryCache

	debug bool

	gateway *satellite.Gateway
	options *Options

	transportTypes satellite.TransportTypeSlice
	interfaces     []Interface

	authHandle AuthHandle
}

type Options struct {
	WebPath    string `json:"web_path"`
	PublishTcp string `json:"publish_tcp"`
	PublishUdp string `json:"publish_udp"`
	TrackId    string `json:"track_id"`
}

func NewServer(logger def.Logger, tcpAddr string, udpAddr string, options Options) (*Server, error) {
	var e = &Server{
		interfaces:     []Interface{},
		transportTypes: []satellite.TransportType{},
	}
	if options.WebPath == "" {
		options.WebPath = "/comet.socket"
	}
	if options.PublishTcp == "" {
		options.PublishTcp = tcpAddr
	}
	options.PublishTcp = parseLocal(options.PublishTcp)

	if options.PublishUdp == "" {
		options.PublishUdp = udpAddr
	}
	options.PublishUdp = parseLocal(options.PublishUdp)

	if options.TrackId == "" {
		options.TrackId = "trackId"
	}
	e.options = &options

	e.gateway = satellite.NewGateway(e)
	if tcpAddr != "" {
		addrTcp, err := net.ResolveTCPAddr("tcp", tcpAddr)
		if err != nil {
			return nil, err
		}
		if err := e.gateway.AddTransport(tcp.NewTransport(addrTcp)); err != nil {
			return nil, err
		}
		e.transportTypes = append(e.transportTypes, satellite.DefaultTransport)
	}
	if udpAddr != "" {
		addrUdp, err := net.ResolveUDPAddr("udp", udpAddr)
		if err != nil {
			return nil, err
		}
		if err := e.gateway.AddTransport(kcp.NewTransport(addrUdp)); err != nil {
			return nil, err
		}
		e.transportTypes = append(e.transportTypes, satellite.SpeedTransport)
	}
	e.gateway.SetCompress(snappy.NewSnappy())
	e.SetLogger(logger)
	return e, nil
}

func (e *Server) Gateway() *satellite.Gateway {
	return e.gateway
}

func (e *Server) Attach(http ws.HttpMux) error {
	err := e.gateway.AddTransport(ws.NewTransportServer(e.options.WebPath, http))
	if err != nil {
		return err
	}
	e.transportTypes = append(e.transportTypes, satellite.SafeTransport)
	return nil
}

func (e *Server) Auth(handle AuthHandle) {
	e.authHandle = handle
}

func (e *Server) Bind(no DN, i Interface) error {
	if len(e.interfaces) > int(no) {
		if e.interfaces[no] != nil {
			return fmt.Errorf("[ Comet ] No %b interface exist", no)
		}
	} else {
		e.interfaces = append(e.interfaces, make([]Interface, int(no)-len(e.interfaces)+1)...)
	}
	e.interfaces[no] = i
	return nil
}

func (e *Server) NewConn(session string) satellite.SessionConn {
	return newServerClient(e, e.gateway, session)
}

func (e *Server) GetSession(code byte, packet *satellite.Packet) (string, error) {
	t := MT(code)
	switch t {
	case P_OPEN:
		return "", nil
	case P_REOPEN:
		return packet.ReadString(), nil
	default:
		return "", nil
	}
}

func (e *Server) SetLogger(logger def.Logger) {
	e.logger = logger.LoggerWrap("satellite", "comet")
	e.gateway.SetLogger(e.logger)
	e.cache = cache.Memory(logger)
}

func (e *Server) SetDebug(debug bool) {
	e.debug = debug
}

func (e *Server) Listen() error {
	e.logger.Debugf("[ Comet ] Listen")
	e.transportTypes.Sort()
	err := e.gateway.Listen()
	if err != nil {
		return err
	}
	return nil
}

func (e *Server) Close() error {
	e.logger.Debugf("[ Comet ] Close")
	if err := e.gateway.Close(); err != nil {
		e.logger.Error(err)
	}
	return nil
}

func (e *Server) ClientNum() int32 {
	return e.gateway.SessionNum()
}

func (e *Server) ConnNum() int32 {
	return e.gateway.ConnNum()
}

func (s *Server) Register(fn func(handler InterfaceHandler) error) func() error {
	return func() error {
		return fn(func(dn DN, i Interface) {
			err := s.Bind(dn, i)
			if err != nil {
				s.logger.Error("[ Comet ] comet define %s is exist", dn)
			} else {
				s.logger.Debugf("[ Comet ] comet interface: %#v", dn)
			}
		})
	}
}

func parseLocal(address string) string {
	var err error
	info := strings.Split(address, ":")
	if len(info) != 2 {
		return address
	}
	if info[0] == "*" || info[0] == "" {
		info[0], err = system.LocalIPV4()
		if err != nil {
			return address
		}
		return strings.Join(info, ":")
	}
	return address
}
